#!/usr/bin/env python

"""Generate a coverage report for an executable.
1) Run the executable under callgrind:
     valgrind --tool=callgrind --callgrind-out-file=CALLGRINDOUTFILE   \
              --dump-instr=yes --compress-strings=no --compress-pos=no \
              EXECUTABLE
2) Use this script to see what didn't execute:
     python kludgecov.py EXECUTABLE CALLGRINDOUTFILE

This will list all lines that did not have every single assembly instr.
execute, so beware of ternary operators and short-circuit eval

TODO:
  Error checking
  Testing on more than trivial cases.
  branch coverage - doable with --collect-jump=yes
"""

import re
import sys
import os
from subprocess import Popen, PIPE
executable = sys.argv[1]

allAddrs = {}

objdump = Popen('objdump -d '+executable, shell=True, stdout=PIPE)
addr2line = Popen('addr2line -e '+executable, shell=True, stdin=PIPE, 
                  stdout=PIPE)
addrRe = re.compile(r'\s*([0-9A-Fa-f]+):')
for line in objdump.stdout:
    m = addrRe.match(line)
    if m:
        addr = m.groups()[0]
        addr2line.stdin.write(addr + '\n')
        file,lineNo = addr2line.stdout.readline().strip().split(':')
        if file.startswith('??') or lineNo == '0':
            continue
        allAddrs[int(addr, 16)] = file+':'+lineNo
        #print addr,':',file, ':', lineNo

addr2line.stdin.close()        

cgFile = open(sys.argv[2])
# First scan down till we find the section related to our shared
# object and/or executable
for line in cgFile:
    if line.startswith('ob='):
        if line.strip().endswith(os.path.abspath(executable)):
            break
for line in cgFile:
    if line.startswith('ob='):
        # Leave when it starts talking about another object.
	### Can I assume it only mentions my object once?
        break
    if line.startswith('0x'):
        addr = int(line.split()[0],16)
        if addr in allAddrs:
            del allAddrs[addr]

unexecuted = allAddrs.values()
toPrint = dict(zip(unexecuted,[1]*len(unexecuted))).keys()
toPrint.sort()
print '\n'.join(toPrint)
